﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using System.Net;
using System.Diagnostics;
using System.Web;
using System.Web.UI;
using System.Web.Configuration;
using Microsoft.SharePoint.Publishing;

namespace Macaw.Wss3.DependencySpiderSolution
{
    public enum LinkType
    {
        Icon,
        Css,
        Hyperlink,
        Script,
        Image
    }

    public delegate DependencyMap.ResourceRow AddOrGetResourceDelegate(DependencyMap dependencymap, string url);
    public delegate Macaw.Wss3.DependencySpiderSolution.DependencyMap.DependencyRow AddDependencyDelegate(DependencyMap dependencymap, Macaw.Wss3.DependencySpiderSolution.DependencyMap.ResourceRow source, Macaw.Wss3.DependencySpiderSolution.DependencyMap.ResourceRow link, LinkType linktype, string linktext);
    public delegate bool AllowIndexingDelegate(string url);

    public class DependencySpiderJobDefinition : SPJobDefinition
    {
        public enum ResourceType
        {
            SharepointFile,
            Code301,
            Code302,
            Code401,
            Image,
            Stylesheet,
            Javascript,
            Page
        }



        public static string JobName = "DependencySpiderJob";
        public static Guid JobId = new Guid("{917F164A-7919-48FF-B473-35FBCF042123}");

        private void Init()
        {
            this.Title = "Dependency spider Checker";
            this.Id = JobId;
            this.Name = JobName;
        }

        public DependencySpiderJobDefinition()
            : base()
        {
        }

        public DependencySpiderJobDefinition(SPWebApplication webApp)
            : base(JobName, webApp, null, SPJobLockType.Job)
        {
            this.Title = "Dependency spider Checker";
        }

        private bool AllowIndexing(string url)
        {
            return false;
        }

        /// <summary>
        /// This method registers the Url as a Resource
        /// </summary>
        /// <param name="dependencymap"></param>
        /// <param name="url">Url which needs to be registered as a resource</param>
        /// <returns>The resource instance</returns>
        private Macaw.Wss3.DependencySpiderSolution.DependencyMap.ResourceRow AddOrGetResource(DependencyMap dependencymap, string url)
        {
            url = HttpUtility.UrlDecode(url);

            Macaw.Wss3.DependencySpiderSolution.DependencyMap.ResourceRow[] resourcerows;

            // Escape the Url
            string filterurl = url.Replace("'", "''");

            // Construct the FilterExpression
            string filter = string.Format("Url='{0}'", filterurl);

            // Get rows with filter
            resourcerows = (Macaw.Wss3.DependencySpiderSolution.DependencyMap.ResourceRow[])dependencymap.Resource.Select(filter);

            Macaw.Wss3.DependencySpiderSolution.DependencyMap.ResourceRow resourcerow;
            switch (resourcerows.Length)
            {
                case 0:
                    // Register the Url as a resource
                    //Trace.WriteLine(string.Format("Adding resource \"{0}\"", url ));
                    resourcerow = dependencymap.Resource.NewResourceRow();
                    resourcerow.Url = url;
                    dependencymap.Resource.AddResourceRow(resourcerow);
                    return resourcerow;
                case 1:
                    return resourcerows[0];
                default:
                    throw new Exception("Multiple resources match the Url.");
            }
        }


        private Macaw.Wss3.DependencySpiderSolution.DependencyMap.DependencyRow AddDependency(DependencyMap dependencymap, Macaw.Wss3.DependencySpiderSolution.DependencyMap.ResourceRow source, Macaw.Wss3.DependencySpiderSolution.DependencyMap.ResourceRow link, LinkType linktype, string linktext)
        {
            Macaw.Wss3.DependencySpiderSolution.DependencyMap.DependencyRow dependencyrow = dependencymap.Dependency.FindBySourceLink(source.Id, link.Id);

            if (dependencyrow == null)
            {
                //Trace.WriteLine(string.Format("Adding dependency to \"{0}\" for \"{1}\"", source.Url, link.Url));
                dependencyrow = dependencymap.Dependency.NewDependencyRow();
                dependencyrow.Source = source.Id;
                dependencyrow.Link = link.Id;
                dependencyrow.Type = linktype.ToString();
                dependencyrow.Text = linktext;
                dependencymap.Dependency.AddDependencyRow(dependencyrow);
            }
            else
            {
                //Trace.WriteLine(string.Format("Dependency already exists to \"{0}\" for \"{1}\"", source.Url, link.Url));
            }
            return dependencyrow;
        }


        private const string FIELDNAME_SOURCEURL = "SourceUrl";
        private const string FIELDNAME_SOURCEURLTEXT = "SourceUrlText";
        private const string FIELDNAME_ERRORURL = "ErrorUrl";
        private const string FIELDNAME_ERRORURLTEXT = "ErrorUrlText";
        private const string FIELDNAME_ERRORFILETYPE = "ErrorFileType";
        private const string FIELDNAME_SOURCETITLE = "SourceTitle";
        private const string FIELDNAME_SITEID = "SiteID";
        private const string FIELDNAME_RESULT = "Result";
        private const string FIELDNAME_IGNORE = "Ignore";

        private SPList CreateReportList(SPWeb web)
        {
            Trace.WriteLine(string.Format("Creating the ReportList at \"{0}\"", web.ServerRelativeUrl));
            Guid reportlistid = web.Lists.Add("DependencyReport", string.Empty, SPListTemplateType.GenericList);
            SPList reportlist = web.Lists[reportlistid];
            reportlist.Fields[FieldId.Title].Title = FIELDNAME_RESULT;
            reportlist.Fields[FieldId.Title].Update();
            reportlist.Fields.Add(FIELDNAME_ERRORURL, SPFieldType.URL, true);
            reportlist.Fields.Add(FIELDNAME_ERRORURLTEXT, SPFieldType.Text, true);
            reportlist.Fields.Add(FIELDNAME_SOURCEURL, SPFieldType.URL, true);
            reportlist.Fields.Add(FIELDNAME_SOURCEURLTEXT, SPFieldType.Text, true);
            reportlist.Fields.Add(FIELDNAME_ERRORFILETYPE, SPFieldType.Text, true);
            reportlist.Fields.Add(FIELDNAME_SOURCETITLE, SPFieldType.Text, true);
            reportlist.Fields.Add(FIELDNAME_SITEID, SPFieldType.Text, true);

            reportlist.Fields.Add(FIELDNAME_IGNORE, SPFieldType.Boolean, false);
            //reportlist.Update();

            Trace.WriteLine("Adding views to ReportingList");
            System.Collections.Specialized.StringCollection blviewfields = new System.Collections.Specialized.StringCollection();
            blviewfields.AddRange(new string[] { FIELDNAME_SOURCEURL, FIELDNAME_ERRORURL, FIELDNAME_SOURCETITLE, FIELDNAME_ERRORFILETYPE, FIELDNAME_RESULT, "Edit" });
            SPView blview = reportlist.Views.Add("Broken links", blviewfields, "<GroupBy Collapse=\"TRUE\" GroupLimit=\"1000\"><FieldRef Name=\"ErrorUrlText\" /></GroupBy><Where><Neq><FieldRef Name=\"Ignore\" /><Value Type=\"Boolean\">1</Value></Neq></Where>", 99999, false, true, SPViewCollection.SPViewType.Html, false);

            System.Collections.Specialized.StringCollection pwblviewfields = new System.Collections.Specialized.StringCollection();            
            pwblviewfields.AddRange(new string[] { FIELDNAME_SOURCEURL, FIELDNAME_ERRORURL, FIELDNAME_SOURCETITLE, FIELDNAME_ERRORFILETYPE, FIELDNAME_RESULT ,"Edit"  });
            SPView pwblview = reportlist.Views.Add("Pages with broken links", pwblviewfields, "<GroupBy Collapse=\"TRUE\" GroupLimit=\"1000\"><FieldRef Name=\"SourceUrlText\" /></GroupBy><Where><Neq><FieldRef Name=\"Ignore\" /><Value Type=\"Boolean\">1</Value></Neq></Where>", 99999, false, true, SPViewCollection.SPViewType.Html, false);

            System.Collections.Specialized.StringCollection mrviewfields = new System.Collections.Specialized.StringCollection();
            mrviewfields.AddRange(new string[] { FIELDNAME_SOURCEURL, FIELDNAME_ERRORURL, FIELDNAME_SOURCETITLE, FIELDNAME_ERRORFILETYPE, FIELDNAME_RESULT, "Edit"});
            SPView mrview = reportlist.Views.Add("Error resources", mrviewfields, "<GroupBy Collapse=\"TRUE\" GroupLimit=\"1000\"><FieldRef Name=\"ErrorUrlText\" /></GroupBy><Where><And><And><Neq><FieldRef Name=\"Ignore\" /><Value Type=\"Boolean\">1</Value></Neq><Neq><FieldRef Name=\"ErrorFileType\" /><Value Type=\"Text\">Hyperlink</Value></Neq></And><Neq><FieldRef Name=\"ErrorFileType\" /><Value Type=\"Text\">Image</Value></Neq></And></Where>", 99999, false, false, SPViewCollection.SPViewType.Html, false);

            System.Collections.Specialized.StringCollection miviewfields = new System.Collections.Specialized.StringCollection();
            miviewfields.AddRange(new string[] { FIELDNAME_SOURCEURL, FIELDNAME_ERRORURL, FIELDNAME_SOURCETITLE, FIELDNAME_ERRORFILETYPE, FIELDNAME_RESULT, "Edit"});
            SPView miview = reportlist.Views.Add("Error images", miviewfields, "<GroupBy Collapse=\"TRUE\" GroupLimit=\"1000\"><FieldRef Name=\"ErrorUrlText\" /></GroupBy><Where><And><Eq><FieldRef Name=\"ErrorFileType\" /><Value Type=\"Text\">Image</Value></Eq><Neq><FieldRef Name=\"Ignore\" /><Value Type=\"Boolean\">1</Value></Neq></And></Where>", 99999, false, false, SPViewCollection.SPViewType.Html, false);

            System.Collections.Specialized.StringCollection ilviewfields = new System.Collections.Specialized.StringCollection();
            ilviewfields.AddRange(new string[] { FIELDNAME_SOURCEURL, FIELDNAME_ERRORURL, FIELDNAME_SOURCETITLE, FIELDNAME_ERRORFILETYPE, FIELDNAME_RESULT, "Edit" });
            SPView ilview = reportlist.Views.Add("Ignored links", ilviewfields, "<Where><Eq><FieldRef Name=\"Ignore\" /><Value Type=\"Boolean\">1</Value></Eq></Where>", 99999, false, false, SPViewCollection.SPViewType.Html, false);

            Trace.WriteLine("ReportingList complete");
            return reportlist;
        }

        public void ReportErrors(SPWeb reportweb, DependencyMap dependencymap, SPSite site)
        {
            Trace.WriteLine(string.Format("Reporting to '{0}'", reportweb.ServerRelativeUrl));
            SPList reportlist = null;
            try
            {
                reportlist = reportweb.Lists["DependencyReport"];
            }
            catch { }
            if (reportlist == null)
            {
                reportlist = CreateReportList(reportweb);
            }

            #region Mark all listitems from this sitecollection as "Not Updated"
            Trace.WriteLine(string.Format("Retrieving all broken links for site \"{0}\"", site.ID));
            SPQuery querySiteErrors = new SPQuery();
            querySiteErrors.Query = string.Format(
                "<Where>" +
                    "<Eq><FieldRef Name='{0}'/>" +
                    "<Value Type='Text'>{1}</Value></Eq>" +
                "</Where>",
                FIELDNAME_SITEID, site.ID.ToString());
            querySiteErrors.RowLimit = 0;

            Trace.WriteLine(querySiteErrors.Query);
            SPListItemCollection previouserrors = null;            
            try
            {
                previouserrors = reportlist.GetItems(querySiteErrors);
                Trace.WriteLine("OK");
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Not OK");
                Trace.WriteLine(ex.Message);
                Trace.WriteLine(ex.ToString());
            }

            Trace.WriteLine(string.Format("Remembering the ID's of '{0}' listitems", previouserrors.Count));
            List<int> previouserrorsids = new List<int>();
            foreach (SPListItem previouserror in previouserrors)
            {
                previouserrorsids.Add(previouserror.ID);
            }
            #endregion

            #region Add new errors
            DependencyMap.ResourceRow[] resourcerows = (DependencyMap.ResourceRow[])dependencymap.Resource.Select("error=true");
            Trace.WriteLine(string.Format("Reporting '{0}' errors", resourcerows.Length));
            foreach (DependencyMap.ResourceRow resourcerow in resourcerows)
            {
                DependencyMap.DependencyRow[] rrowdependencyrows = resourcerow.GetDependencyRowsByResource_Link_Dependency();
                if (rrowdependencyrows.Length == 0)
                {
                    Trace.WriteLine(string.Format("Warning! Error Url \"{0}\" could not be reported as no source-link was returned", resourcerow.Url));
                }

                foreach (DependencyMap.DependencyRow rrowdependencyrow in rrowdependencyrows)
                {
                    DependencyMap.ResourceRow sourcerow = rrowdependencyrow.ResourceRowByFK_Resource_Source_Dependency;

                    SPQuery query = new SPQuery();
                    query.Query = string.Format(
                        "<Where>" +
                        "<And>" +
                            "<Eq><FieldRef Name='{0}'/>" +
                            "<Value Type='Text'>{1}</Value></Eq>" +
                            "<And>" +
                                "<Eq><FieldRef Name='{2}'/>" +
                                "<Value Type='Text'>{3}</Value></Eq>" +
                                "<Eq><FieldRef Name='{4}'/>" +
                                "<Value Type='Text'>{5}</Value></Eq>" +
                            "</And>" +
                        "</And>" +
                        "</Where>",
                        FIELDNAME_ERRORURLTEXT, resourcerow.Url,
                        FIELDNAME_SOURCEURLTEXT, sourcerow.Url,
                        FIELDNAME_SITEID, site.ID.ToString());
                    query.RowLimit = 0;
                    SPListItemCollection result = reportlist.GetItems(query);
                    if (result == null || result.Count == 0)
                    {
                        Trace.WriteLine(string.Format("Error reported: ErrorUrl '{0}'  SourceUrl '{1}'", resourcerow.Url, sourcerow.Url));
                        SPListItem item = reportlist.Items.Add();
                        item[FIELDNAME_ERRORURLTEXT] = resourcerow.Url;
                        item[FIELDNAME_ERRORURL] = resourcerow.Url;
                        item[FIELDNAME_SOURCEURLTEXT] = sourcerow.Url;
                        item[FIELDNAME_SOURCEURL] = sourcerow.Url;
                        item[FIELDNAME_SOURCETITLE] = rrowdependencyrow.Text;
                        item[FIELDNAME_SITEID] = site.ID.ToString();
                        item[FIELDNAME_RESULT] = resourcerow.ContentType;
                        item[FIELDNAME_ERRORFILETYPE] = rrowdependencyrow.Type.ToString();
                        item.SystemUpdate();
                    }
                    else
                    { // Error items were already reported
                        Trace.WriteLine(string.Format("Error updated: ErrorUrl '{0}'  SourceUrl '{1}'", resourcerow.Url, sourcerow.Url));
                        foreach (SPListItem previouserror in result)
                        {
                            previouserrorsids.Remove(previouserror.ID);
                        }
                    }
                }
            }
            #endregion

            #region Delete the "Not Updated" listitems from the reportlist. 
            Trace.WriteLine(string.Format("There will be \"{0}\" items removed from the reportlist as these errors no longer occur" , previouserrorsids.Count));
            foreach (int ID in previouserrorsids)
            {
                try
                {
                    reportlist.Items.DeleteItemById(ID);
                }
                catch
                {
                    Trace.WriteLine(string.Format("Unable to delete not-updated item \"{0}\" from the reportlist",ID));
                }
            }

            #endregion

        }

        public void ProcessSite(DependencyMapStorageCollection storagecollection, SPSite site)
        {
            Trace.WriteLine(string.Format("{0}: Processing Sitecollection '{1}'. ", this.GetType().FullName, site.MakeFullUrl(site.ServerRelativeUrl)));

            using (SPSite systemSite = new SPSite(site.ID, site.SystemAccount.UserToken))
            {
                DependencyMapStorage dependencymapstorage = storagecollection.GetValue<DependencyMapStorage>(site.ID);
                DependencyMap dependencymap = new DependencyMap();
                if (dependencymapstorage == null)
                {
                    dependencymapstorage = new DependencyMapStorage(site.ID.ToString(), WebApplication, site.ID);
                    storagecollection.Add(dependencymapstorage);
                }
                else
                {
                    dependencymapstorage.dependencymap = null;
                }
                dependencymapstorage.SpideredItemCount = 0;
                dependencymapstorage.SpideredData = 0;
                dependencymapstorage.SpideringStarted = DateTime.MinValue;
                dependencymapstorage.SpideringCompleted = DateTime.MinValue;
                dependencymapstorage.IndexingStarted = DateTime.Now;

                #region Indexing
                SPSiteIndexer spsiteindexer = new SPSiteIndexer(dependencymap, new AddOrGetResourceDelegate(AddOrGetResource));
                spsiteindexer.Index(systemSite);

                dependencymapstorage.IndexingCompleted = DateTime.Now;
                dependencymapstorage.IndexedItemCount = spsiteindexer.IndexCount;
                #endregion

                #region Spidering
                dependencymapstorage.SpideringStarted = DateTime.Now;
                SPSiteSpider spsitespider = new SPSiteSpider(dependencymap, new AddOrGetResourceDelegate(AddOrGetResource), new AddDependencyDelegate(AddDependency));
                spsitespider.Spider(systemSite);

                dependencymapstorage.SpideringCompleted = DateTime.Now;
                dependencymapstorage.SpideredItemCount = spsitespider.SpiderCount;
                dependencymapstorage.SpideredData = spsitespider.SpiderData;
                #endregion

                using (SPWeb web = site.OpenWeb(site.RootWeb.ID))
                {
                    if (web.Properties.ContainsKey(DependencySpiderFeatureReceiver.DEPENDENCYSPIDERSTOREMAPKEY))
                    {
                        bool storemap = false;
                        bool.TryParse(web.Properties[DependencySpiderFeatureReceiver.DEPENDENCYSPIDERSTOREMAPKEY], out storemap);
                        if (storemap)
                        {
                            System.IO.StringWriter sw = new System.IO.StringWriter();
                            dependencymap.WriteXml(sw);
                            dependencymapstorage.dependencymap = sw.GetStringBuilder().ToString();

                            dependencymapstorage.Update();
                        }
                    }
                }

                #region Reporting to list
                using (SPWeb web = site.OpenWeb(site.RootWeb.ID))
                {
                    if (web.Properties.ContainsKey(DependencySpiderFeatureReceiver.DEPENDENCYSPIDERREPORTKEY))
                    {
                        string reporturl = web.Properties[DependencySpiderFeatureReceiver.DEPENDENCYSPIDERREPORTKEY];
                        if (!string.IsNullOrEmpty(reporturl))
                        {
                            Trace.WriteLine("Reporting to: " + reporturl);

                            using (SPSite reportsite = new SPSite(reporturl))
                            {
                                using (SPSite reportsite2 = new SPSite(reporturl, reportsite.SystemAccount.UserToken))
                                {
                                    using (SPWeb reportweb = reportsite2.OpenWeb())
                                    {
                                        ReportErrors(reportweb, dependencymap, site);
                                    }
                                }
                            }
                        }
                    }
                }
                #endregion
            }
        }

        public override void Execute(Guid targetInstanceId)
        {
            Trace.WriteLine(string.Format("{0}: Execute - start", this.GetType().FullName));
            try
            {
                // Create a storagecollection instance
                DependencyMapStorageCollection storagecollection = new DependencyMapStorageCollection(WebApplication);

                #region Loop through all SiteCollection and Process them when enabled
                for (int idx = WebApplication.Sites.Count - 1; idx >= 0; idx--)
                {
                    using (SPSite site = WebApplication.Sites[idx])
                    {
                        Trace.WriteLine(string.Format("{0}: Sitecollection '{1}'.", this.GetType().FullName, site.MakeFullUrl(site.ServerRelativeUrl)));

                        bool enabledstate = false;
                        #region Check wether this sitecollection is marked "enabled".
                        using (SPWeb web = site.OpenWeb(site.RootWeb.ID))
                        {
                            if (web.Properties.ContainsKey(DependencySpiderFeatureReceiver.DEPENDENCYSPIDERENABLEDKEY))
                            {
                                bool.TryParse(web.Properties[DependencySpiderFeatureReceiver.DEPENDENCYSPIDERENABLEDKEY], out enabledstate);
                            }
                        }
                        #endregion

                        if (enabledstate)
                        {
                            ProcessSite(storagecollection, site);
                        }
                        else
                        {
                            Trace.WriteLine(string.Format("{0}: DependencySpider is not enabled for Sitecollection '{1}'.", this.GetType().FullName, site.MakeFullUrl(site.ServerRelativeUrl)));
                        }
                    }
                }
                #endregion
            }
            catch (Exception ex)
            {
                Trace.WriteLine(string.Format("{0}: Error\r\n{1}", this.GetType().FullName, ex.ToString()));
            }

            base.Execute(targetInstanceId);
            Trace.WriteLine(string.Format("{0}: Execute - end", this.GetType().FullName));
        }
    }
}
